// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

/*
 * Utf8TextCoder.cpp
 *
 *  Created on: Dec 15, 2009
 *      Author: dhovemey
 */

#include "util/InputStream.h"
#include "util/OutputStream.h"
#include "util/TextEncodingException.h"
#include "util/String.h"
#include "util/Utf8TextCoder.h"

Utf8TextCoder::Utf8TextCoder()
{

}

Utf8TextCoder::~Utf8TextCoder()
{

}

long Utf8TextCoder::decode(InputStream *in)
{
	int leadByte = in->read();
	if (leadByte < 0) {
		return -1; // end of file
	}

	if (startOf1ByteSequence(leadByte)) {
		return long(leadByte);
	}

	if (startOf2ByteSequence(leadByte)) {
		return decode2ByteSequence(leadByte, in);
	}

	if (startOf3ByteSequence(leadByte)) {
		return decode3ByteSequence(leadByte, in);
	}

	if (startOf4ByteSequence(leadByte)) {
		return decode4ByteSequence(leadByte, in);
	}

	throw TextEncodingException(
			std::wstring(L"Invalid lead byte in UTF-8 sequence: ") + String::asWString(leadByte));
}

void Utf8TextCoder::encode(wchar_t c, OutputStream *out)
{
	if(requires1ByteSequence(c)) {
		out->write(c);
	} else if (requires2ByteSequence(c)) {
		encode2ByteSequence(c, out);
	} else if (requires3ByteSequence(c)) {
		encode3ByteSequence(c, out);
	} else if (requires4ByteSequence(c)) {
		encode4ByteSequence(c, out);
	} else {
		//throw IOException(String::format("Invalid UCS code point: %ld", long(c)));
		throw IOException(std::wstring(L"Invalid UCS code point: ") + String::asWString(int32_t(c)));
	}
}

void Utf8TextCoder::checkEOF(int c)
{
    if(c < 0){
        throw TextEncodingException(L"Unexpected EOF in UTF-8 sequence");
    }
}

void Utf8TextCoder::checkContinuationByte(int c)
{
    checkEOF(c);
    if ((c & 0xC0) != 0x80) {
		throw TextEncodingException(
				std::wstring(L"Invalid continuation byte in UTF-8 sequence:d") + String::asWString(c));
	}
}

// One-byte encodings:
// 0xxxxxxx

bool Utf8TextCoder::startOf1ByteSequence(int leadByte)
{
	return leadByte < 128;
}

// Two-byte encodings:
// 110yyyxx 10xxxxxx

bool Utf8TextCoder::startOf2ByteSequence(int leadByte)
{
	return (leadByte & 0xE0) == 0xC0;
}

int Utf8TextCoder::decode2ByteSequence(int leadByte, InputStream *in)
{
	int result = 0;

	int c2 = in->read();
	checkContinuationByte(c2);

	result |= ((leadByte & 0x1F) << 6);  // first byte has bits 6..10
	result |= (c2 & 0x3F);               // second byte has bits 0..5
	return result;
}

// Three-byte encodings:
// 1110yyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::startOf3ByteSequence(int leadByte)
{
	return (leadByte & 0xF0) == 0xE0;
}

int Utf8TextCoder::decode3ByteSequence(int leadByte, InputStream *in)
{
	int result = 0;

//	checkContinuationByte(buf[1]);
//	checkContinuationByte(buf[2]);

	int c2 = in->read();
	checkContinuationByte(c2);

	int c3 = in->read();
	checkContinuationByte(c3);

	result |= ((leadByte & 0xF0) << 12);  // first byte has bits 12..15
	result |= (c2 & 0x3F) << 6;           // second byte has bits 6..11
	result |= (c3 & 0x3F);                // third byte has bits 0..5
	return result;
}

// Four-byte encodings:
// 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::startOf4ByteSequence(int leadByte)
{
	return (leadByte & 0xF8) == 0xF0;
}

int Utf8TextCoder::decode4ByteSequence(int leadByte, InputStream *in)
{
	int result = 0;

	int c2 = in->read();
	checkContinuationByte(c2);

	int c3 = in->read();
	checkContinuationByte(c3);

	int c4 = in->read();
	checkContinuationByte(c4);

	result |= ((leadByte & 0x07) << 18);   // first byte has bits 19..21
	result |= ((c2 & 0x3F) << 12);         // second byte has bits 12..17
	result |= ((c3 & 0x3F) << 6);          // third byte has bits 6.11
	result |= (c4 & 0x3F);                 // fourth byte has bits 0..5
	return result;
}

// U+0000–U+007F 0xxxxxxx

bool Utf8TextCoder::requires1ByteSequence(wchar_t c)
{
	return c <= 0x7F;
}

// U+0080–U+07FF 110yyyxx 10xxxxxx

bool Utf8TextCoder::requires2ByteSequence(wchar_t c)
{
	return c >= 0x80 && c <= 0x7FF;
}

void Utf8TextCoder::encode2ByteSequence(wchar_t c, OutputStream *out)
{
	out->write(0xC0 | ((c >> 6) & 0x1F)); // first byte contains bits 6..10
	out->write(0x80 | (c & 0x3F));        // second byte contains bits 0..5
}

// U+0800–U+FFFF 1110yyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::requires3ByteSequence(wchar_t c)
{
	return c >= 0x800 && c <= 0xFFFF;
}

void Utf8TextCoder::encode3ByteSequence(wchar_t c, OutputStream *out)
{
	out->write(0xD0 | ((c >> 12) & 0x0F)); // first byte contains bits 12..15
	out->write(0x80 | ((c >> 6) & 0x3F));  // second byte contains bits 6..11
	out->write(0x80 | (c & 0x3F));         // third byte contains bits 0..5
}

// U+10000–U+10FFFF 11110zzz 10zzyyyy 10yyyyxx 10xxxxxx

bool Utf8TextCoder::requires4ByteSequence(wchar_t c)
{
	return c >= 0x10000 && c <= 0x10FFFF;
}

void Utf8TextCoder::encode4ByteSequence(wchar_t c, OutputStream *out)
{
	out->write(0xF0 | ((c >> 18) & 0x07)); // first byte has bits 18..20
	out->write(0x80 | ((c >> 12) & 0x3F)); // second byte has 12..17
	out->write(0x80 | ((c >> 6) & 0x3F));  // third byte has bits 6..11
	out->write(0x80 | (c & 0x3F));         // fourth byte has bits 0..5
}
